package com.quectel.util.client;

import com.google.common.base.Preconditions;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import lombok.Data;

import javax.validation.constraints.NotNull;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <pre>
 *  多个客户端的管理器，慎用除非你很精确的知道怎么用
 *  支持的特性:
 *      1.可以重连
 *      2.可以长时间不用销毁
 *
 *  泛型 S代表配置 C代表产生的实例
 *
 *  详细使用方法查看 com.quectel.util.client.MultiClientManagerTests
 * </pre>
 *
 * @author: caolu
 * @email: louis.cao@quectel.com
 * @date: 2023-05-23 14:30:00
 */
public class MultiClientManager<S, C> {
    /**
     * 缓存
     */
    private Cache<String, Value<S, C>> cache;

    /**
     * 扫描线程
     */
    private final ScheduledExecutorService scanThread = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
        private AtomicInteger atomicInteger = new AtomicInteger(0);

        @Override
        public Thread newThread(@NotNull Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            t.setName("MultiClientManager scan thread-" + atomicInteger.incrementAndGet());
            return t;
        }
    });

    private ClientFactory<S, C> factory;


    private MultiClientManager() {

    }

    public MultiClientManager(ClientFactory<S, C> factory) {

        Preconditions.checkNotNull(factory);

        this.factory = factory;

        if (factory.expireDuration() != null) {
            this.cache = CacheBuilder
                    .newBuilder()
                    .expireAfterAccess(factory.expireDuration())
                    .removalListener((RemovalListener<String, Value<S, C>>) notification -> {
                        if (notification.getValue() != null) {
                            // 需要销毁实例
                            factory.shutdown(notification.getValue().getC());
                        }
                    }).build();
        } else {
            this.cache = CacheBuilder
                    .newBuilder().build();
        }

        startScanThread();
    }

    /**
     * 启动扫描线程
     */
    private void startScanThread() {
        scanThread.scheduleAtFixedRate(
                () -> {
                    for (Map.Entry<String, Value<S, C>> entry : cache.asMap().entrySet()) {
                        Value<S, C> value = entry.getValue();
                        if (!factory.valid(value.getC())) {
                            factory.shutdown(value.getC());
                            // 重新产生实例/重连
                            C remakedC = factory.make(value.getS());
                            if (remakedC != null) {
                                // 可以重连成功
                                value.setC(remakedC);
                            }

                        }
                    }
                }, 0, 1, TimeUnit.MINUTES
        );
    }

    /**
     * 获取缓存的客户端
     *
     * @param uniqueClientName 客户端唯一标识
     * @param s                配置
     * @return
     */
    public C getCachedClient(String uniqueClientName, S s) {

        Preconditions.checkArgument(uniqueClientName != null && s != null);

        return cache.asMap().computeIfAbsent(uniqueClientName, key -> {
            C c = factory.make(s);
            Preconditions.checkNotNull(c);
            Value<S, C> value = new Value<>();
            value.setS(s);
            value.setC(c);
            value.setCreateAt(System.currentTimeMillis());
            value.setExpireDuration(factory.expireDuration());
            return value;
        }).getC();
    }

    /**
     * 手动删除客户端
     *
     * @param uniqueClientName
     */
    public void removeClient(String uniqueClientName) {
        Value<S, C> value = cache.getIfPresent(uniqueClientName);
        if (value != null) {
            factory.shutdown(value.getC());
        }
        cache.invalidate(uniqueClientName);
    }


    /**
     * 对象的创建工厂
     */
    public interface ClientFactory<S, C> {
        /**
         * 创建对象
         *
         * @param s 配置
         * @return
         */
        C make(S s);

        /**
         * 是否有效 如果返回false则移除该实例 这个方法需要特别注意不能阻塞太长
         *
         * @param c
         * @return
         */
        boolean valid(C c);

        /**
         * 销毁实例 在里边写一写释放逻辑
         *
         * @param c
         */
        void shutdown(C c);

        /**
         * 默认永久有效， 有效期为最后一次访问时间
         *
         * @return
         */
        default Duration expireDuration() {
            return null;
        }

    }

    /**
     * 缓存对象
     *
     * @param <S>
     * @param <C>
     */
    @Data
    private static final class Value<S, C> {
        private S s;
        private C c;
        private long createAt;
        private Duration expireDuration;
    }

}
